#!/usr/bin/env python
# -*- coding:utf8 -*-
# uncompyle6 version 2.15.1
# Python bytecode 2.7 (62211)
# Decompiled from: Python 2.7.10 (default, Jul  1 2017, 13:36:56) 
# [GCC 4.4.6 20110731 (Red Hat 4.4.6-4)]
# Embedded file name: ./esb/esb/bkapp/validators.py
# Compiled at: 2017-11-16 15:44:28
"""
Tencent is pleased to support the open source community by making 蓝鲸智云(BlueKing) available.
Copyright (C) 2017 THL A29 Limited, a Tencent company. All rights reserved.
Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at http://opensource.org/licenses/MIT
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and limitations under the License.
"""
import time, hmac, base64, hashlib
from common.base_validators import BaseValidator, ValidationError
from common.errors import error_codes
from esb.exdb.bkpaas import AppSecureInfo

class AppAuthValidator(BaseValidator):

    def __init__(self, verified_type='signature_or_app_secret', *args, **kwargs):
        """
        :param str verified_type: 验证类型，支持"app_secret", "signature", "signature_or_app_secret"
        """
        self.verified_type = verified_type
        super(AppAuthValidator, self).__init__(*args, **kwargs)

    def validate(self, request):
        if self.verified_type == 'app_secret':
            validator = AppSecretValidator()
            validator.validate(request)
            return
        if self.verified_type == 'signature':
            validator = SignatureValidator()
            validator.validate(request)
            return
        if self.verified_type == 'signature_or_app_secret':
            signature = request.GET.get('signature')
            app_secret = request.g.kwargs.get('app_secret')
            if signature:
                validator = SignatureValidator()
                validator.validate(request)
            else:
                if app_secret:
                    validator = AppSecretValidator()
                    validator.validate(request)
                else:
                    raise ValidationError(u'请求签名 [signature] 和 应用TOKEN [app_secret] 不能同时为空')
            return
        raise ValidationError(u'请选择有效的APP验证方式')


class AppSecretValidator(BaseValidator):
    """
    Validate app_code and app_secret
    """

    def __init__(self, *args, **kwargs):
        super(AppSecretValidator, self).__init__(*args, **kwargs)

    def validate(self, request):
        kwargs = request.g.kwargs
        app_code = kwargs.get('app_code')
        app_secret = kwargs.get('app_secret')
        if not app_code:
            raise ValidationError(u'应用ID [app_code] 不能为空')
        if not app_secret:
            raise ValidationError(u'应用TOKEN [app_secret] 不能为空')
        app_info = AppSecureInfo.get_by_app_code(app_code)
        if not app_info:
            raise ValidationError(u'无效的应用ID [app_code=%s]，请确认应用ID是否注册' % app_code)
        if app_secret not in app_info['secure_key_list']:
            raise ValidationError(u'应用TOKEN验证失败，请确认应用TOKEN与应用ID [app_code=%s] 是否匹配' % app_code)


class SignatureValidator(BaseValidator):
    """
    Validate signature
    """

    def __init__(self, *args, **kwargs):
        super(SignatureValidator, self).__init__(*args, **kwargs)

    def get_request_path(self, request):
        """
        为了应对使用proxy_pass拿不到完整path的情况，先尝试获取自定义头信息，再尝试 path_info
        """
        path = request.META.get('HTTP_X_REQUEST_URI', '').split('?')[0]
        if not path:
            path = request.META['PATH_INFO']
        return path

    def validate(self, request):
        if getattr(request, '__esb_skip_signature__', False):
            return
        req_get_params = dict(request.GET.items())
        signature = req_get_params.pop('signature', None)
        if not signature:
            raise ValidationError(u'请求签名 [signature] 不能为空')
        app_info = self.check_app_code(request.g.kwargs.get('app_code'))
        self.check_nonce(req_get_params.get('bk_nonce'))
        self.check_timestamp(req_get_params.get('bk_timestamp'))
        path = self.get_request_path(request)
        params = req_get_params.copy()
        if request.method == 'POST':
            params['data'] = request.body
        verify_result = self.verify_signature(request.method, path, params, signature, app_info['secure_key_list'])
        if not verify_result:
            raise ValidationError(u'请求签名 [signature] 校验失败，请提供有效的参数和signature')
        return

    def verify_signature(self, method, path, params, signature, valid_app_secret_list):
        """
        校验signature有效
        """
        req_params = ('&').join([ '%s=%s' % (k, v) for k, v in sorted(params.iteritems(), key=lambda x: x[0])
                                ])
        message = '%s%s?%s' % (method, path, req_params)
        for valid_app_secret in valid_app_secret_list:
            sign = base64.b64encode(hmac.new(str(valid_app_secret), message, hashlib.sha1).digest())
            if cmp(sign, signature) == 0:
                return True

        return False

    def check_app_code(self, app_code):
        """
        验证 app_code
        """
        if not app_code:
            raise ValidationError(u'应用ID [app_code] 不能为空')
        app_info = AppSecureInfo.get_by_app_code(app_code)
        if not app_info:
            raise ValidationError(u'无效的应用ID [app_code=%s]，请确认应用ID是否注册' % app_code)
        return app_info

    def check_nonce(self, bk_nonce):
        """
        验证 bk_nonce
        """
        if not bk_nonce:
            raise ValidationError(u'参数 bk_nonce 不存在')
        try:
            nonce = int(bk_nonce)
        except:
            raise ValidationError(u'参数 bk_nonce 非法')

        if nonce <= 0:
            raise ValidationError(u'参数 bk_nonce 非法，必须是正整数')
        return nonce

    def check_timestamp(self, bk_timestamp):
        """
        验证时间戳是否合法
        """
        if not bk_timestamp:
            raise ValidationError(u'参数 bk_timestamp 不存在')
        try:
            timestamp = int(bk_timestamp)
        except:
            raise ValidationError(u'参数 bk_timestamp 非法，非时间格式')

        if timestamp < int(time.time()) - 300:
            raise ValidationError(u'参数 bk_timestamp 非法，已经过期')
        return timestamp


class AppCodeWhiteListValidator(BaseValidator):

    def __init__(self, white_list=(), *args, **kwargs):
        self.white_list = white_list
        super(AppCodeWhiteListValidator, self).__init__(*args, **kwargs)

    def validate(self, request):
        app_code = request.g.app_code
        if app_code not in self.white_list:
            raise error_codes.APP_PERMISSION_DENIED.format_prompt(u'该组件不允许当前应用(app_code=%s)访问。' % app_code)
